/**
 * 自动化快照每日归档
 *
 * @description 将 `automation-run-snapshots/` 下按运行产生的目录，按“天”聚合压缩为一个 zip
 * 文件，默认仅归档“已过期的日期”（早于今天）。可通过配置开关决定是否同时归档当天并在成功后
 * 是否删除原始目录。
 */

import fssync from 'node:fs'
import fs from 'node:fs/promises'
import path from 'node:path'
import dayjs from 'dayjs'
import { getConfig } from '../../config/index'
import { getLogger } from '../../logger/index'

function getBaseDir(): string {
  return path.resolve(process.cwd(), 'automation-run-snapshots')
}

function getArchivesDir(): string {
  return path.join(getBaseDir(), 'archives')
}

function isRunDirName(name: string): boolean {
  // 形如: 2025-10-08_21-46-32_123_name
  return /^\d{4}-\d{2}-\d{2}_[0-2]\d-[0-5]\d-[0-5]\d_/.test(name)
}

function extractDate(name: string): string | null {
  const m = name.match(/^(\d{4}-\d{2}-\d{2})_/)
  return m ? m[1] : null
}

async function ensureDir(p: string): Promise<void> {
  await fs.mkdir(p, { recursive: true })
}

async function pathExists(p: string): Promise<boolean> {
  try { await fs.access(p); return true }
  catch { return false }
}

async function removeDirSafe(p: string): Promise<void> {
  try { await fs.rm(p, { recursive: true, force: true }) }
  catch {}
}

async function withLock(lockPath: string, fn: () => Promise<void>): Promise<void> {
  // 简单文件锁：存在则跳过
  if (fssync.existsSync(lockPath))
    return
  fssync.writeFileSync(lockPath, String(Date.now()))
  try {
    await fn()
  }
  finally {
    try { fssync.unlinkSync(lockPath) }
    catch {}
  }
}

async function createZip(targetZip: string, dirs: string[]): Promise<void> {
  // 动态导入，避免未安装依赖时影响主流程
  let archiver: any
  try {
    const mod = await import('archiver') as any
    archiver = mod.default || mod
  }
  catch {
    const logger = getLogger()
    logger.warn('归档模块缺少依赖 archiver，已跳过每日压缩')
    throw new Error('missing-archiver')
  }

  await ensureDir(path.dirname(targetZip))

  await new Promise<void>((resolve, reject) => {
    const output = fssync.createWriteStream(targetZip)
    const archive = archiver('zip', { zlib: { level: 9 } })

    output.on('close', () => resolve())
    output.on('error', reject)
    archive.on('error', reject)

    archive.pipe(output)
    for (const dir of dirs) {
      // 第二个参数指定 zip 内的目录名
      const name = path.basename(dir)
      archive.directory(dir, name)
    }
    void archive.finalize()
  })
}

export interface ArchiveOptions {
  /** 是否归档今天（默认仅归档早于今天的日期） */
  includeToday?: boolean
  /** 归档成功后是否删除原始目录（默认 false） */
  removeOriginal?: boolean
}

/**
 * 执行每日快照归档
 *
 * @description 将 baseDir 下形如 `YYYY-MM-DD_HH-mm-ss_xxx` 的目录，聚合到
 * `archives/YYYY-MM-DD.zip`。若 zip 已存在则跳过。
 */
export async function archiveDailySnapshots(options?: ArchiveOptions): Promise<void> {
  const logger = getLogger()
  const baseDir = getBaseDir()
  if (!fssync.existsSync(baseDir))
    return

  // 配置开关
  let includeToday = false
  let removeOriginal = false
  let enabled = false
  try {
    const cfg: any = getConfig()
    enabled = !!(cfg?.logging?.archive_snapshots_enabled)
    includeToday = !!(cfg?.logging?.archive_snapshots_today)
    removeOriginal = !!(cfg?.logging?.remove_original_snapshots_after_archive)
  }
  catch {}
  if (!enabled)
    return
  if (options) {
    if (typeof options.includeToday === 'boolean')
      includeToday = options.includeToday
    if (typeof options.removeOriginal === 'boolean')
      removeOriginal = options.removeOriginal
  }

  const today = dayjs().format('YYYY-MM-DD')
  const entries = await fs.readdir(baseDir, { withFileTypes: true })
  const dayToDirs = new Map<string, string[]>()

  for (const ent of entries) {
    if (!ent.isDirectory())
      continue
    const name = ent.name
    if (!isRunDirName(name))
      continue
    const day = extractDate(name)
    if (!day)
      continue
    if (!includeToday && day >= today)
      continue
    const arr = dayToDirs.get(day) || []
    arr.push(path.join(baseDir, name))
    dayToDirs.set(day, arr)
  }

  if (dayToDirs.size === 0)
    return

  await ensureDir(getArchivesDir())

  for (const [day, dirs] of dayToDirs.entries()) {
    try {
      const zipPath = path.join(getArchivesDir(), `${day}.zip`)
      const lockPath = `${zipPath}.lock`
      if (await pathExists(zipPath)) {
        // 已存在，跳过（避免重复归档）
        continue
      }
      await withLock(lockPath, async () => {
        logger.info(`开始归档快照: ${day}（${dirs.length} 个目录）`)
        await createZip(zipPath, dirs)
        logger.info(`归档完成: ${zipPath}`)
        if (removeOriginal) {
          for (const d of dirs) {
            await removeDirSafe(d)
          }
          logger.info(`已删除原始目录: ${dirs.length} 个`)
        }
      })
    }
    catch (e: any) {
      const msg = String(e?.message || e)
      // 缺少依赖则仅提示一次
      if (msg !== 'missing-archiver')
        logger.warn(`快照归档失败: ${day} -> ${msg}`)
    }
  }
}
